/**********************************************************/
/* Goblin Defense - A tower defense game for Wii Homebrew */
/* Copyright (C) 2009 Ricardo Bueno Cordeiro              */
/* Licensed under de GNU General Public License v2.0      */
/*   See license_gplv2.txt for detalis                    */
/**********************************************************/

#include <stdio.h>

#include <gccore.h>

#include <Metaphrasis.h>
#include <FreeTypeGX.h>
#include "font_ttf.h"

#include "image_png.h"
#include "spritetex_png.h"
#include "map_def.h"
#include "map.h"

#include "draw.h"

u8 error_buffer[] __attribute__ ((aligned (32))) = { 
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

struct texture_t {
	int loaded;
	GXTexObj texture;
};

GXTexObj error_texture;
static struct texture_t sprite_texture;
static struct texture_t maps_texture[NUM_MAPS];

// Mode
static u32 current_mode;

// 2D Graphics
static Mtx44 projection;
static Mtx graph_modelview;
static Mtx text_modelview;

// Font
static FreeTypeGX *freeTypeGX;

// Dimensions
static f32 screen_width;
static f32 screen_height;

void DR_set_error_texture() {
	GX_LoadTexObj(&error_texture, GX_TEXMAP0);
	GX_Flush();
}

void DR_set_sprite_texture() {
	if (sprite_texture.loaded)
		GX_LoadTexObj(&sprite_texture.texture, GX_TEXMAP0);
	else
		GX_LoadTexObj(&error_texture, GX_TEXMAP0);
	GX_Flush();
}

void DR_set_map_texture(unsigned int map) {
	if (map >= NUM_MAPS) {
		GX_LoadTexObj(&error_texture, GX_TEXMAP0);
		GX_Flush();
		return;
	}

	if (maps_texture[map].loaded)
		GX_LoadTexObj(&maps_texture[map].texture, GX_TEXMAP0);
	else
		GX_LoadTexObj(&error_texture, GX_TEXMAP0);
	GX_Flush();
}

void DR_load_texture_from_png(struct texture_t *texture, const u8 *png_buffer, u32 png_size) {
	u32 width, height;

	texture->loaded = 0;
	
	u32 *buffer = load_png_to_RGBA(png_buffer, png_size, &width, &height);
	if (buffer == NULL) {
		return;
	}

	u32 *rgba8_buffer = Metaphrasis::convertBufferToRGBA8(buffer, width, height);
	if (rgba8_buffer == NULL) {
		free(buffer);
		return;
	}

	GX_InitTexObj(&texture->texture, rgba8_buffer, width, height, GX_TF_RGBA8, GX_CLAMP, GX_CLAMP, GX_FALSE);
	DCFlushRange(rgba8_buffer, width * height * 4);	

	free(buffer);
	
	texture->loaded = 1;
}

void DR_load_textures() {
	GX_InitTexObj(&error_texture, error_buffer, 4, 4, GX_TF_RGBA8, GX_CLAMP, GX_CLAMP, GX_FALSE);

	memset(&sprite_texture, 0, sizeof(sprite_texture));
	memset(maps_texture, 0, sizeof(maps_texture));
	
	DR_load_texture_from_png(&sprite_texture, spritetex_png, spritetex_png_size);
	for(int i = 0; i < NUM_MAPS; i++)
		DR_load_texture_from_png(&maps_texture[i], backgrounds[i].data, *backgrounds[i].len);

	DR_set_sprite_texture();
}

void DR_load_font() {
	freeTypeGX = new FreeTypeGX(GX_TF_RGBA8, GX_POS_XYZ);
	freeTypeGX->loadFont(font_ttf, font_ttf_size, FONT_SIZE);
}

void DR_init(f32 width, f32 height) {
	DR_load_textures();
	DR_load_font();

	screen_width = width;
	screen_height = height;

	// Set up text modelview matrix
	guMtxIdentity(text_modelview);
	text_modelview[1][1] = -1.0;

	// Set up graphics modelview matrix
	guMtxIdentity(graph_modelview);

	// Set up projection matrix
	guOrtho(projection, MAP_SIZE / 2, -MAP_SIZE / 2, -MAP_SIZE / 2, ((f32)MAP_SIZE) * 4.0 / 3.0 - ((f32)MAP_SIZE) / 2.0, -10, 10);
	GX_LoadProjectionMtx(projection, GX_ORTHOGRAPHIC);

	DR_configure(DRAW_CONF_TEXT);
}

static void DR_configure_text() {
	GX_ClearVtxDesc();
	GX_SetVtxDesc(GX_VA_POS, GX_DIRECT);
 	GX_SetVtxDesc(GX_VA_CLR0, GX_DIRECT);
	GX_SetVtxDesc(GX_VA_TEX0, GX_DIRECT);

	GX_LoadPosMtxImm(text_modelview, GX_PNMTX0);
	GX_LoadNrmMtxImm(text_modelview, GX_PNMTX0);

	GX_Flush();
}

static void DR_configure_draw_with_texture() {
	GX_ClearVtxDesc();
	GX_SetVtxDesc(GX_VA_POS, GX_DIRECT);
 	GX_SetVtxDesc(GX_VA_CLR0, GX_DIRECT);
	GX_SetVtxDesc(GX_VA_TEX0, GX_DIRECT);

	GX_LoadPosMtxImm(graph_modelview, GX_PNMTX0);
	GX_LoadNrmMtxImm(graph_modelview, GX_PNMTX0);

	GX_SetTevOp(GX_TEVSTAGE0, GX_REPLACE);

	GX_Flush();
}

static void DR_configure_draw_without_texture() {
	GX_ClearVtxDesc();
	GX_SetVtxDesc(GX_VA_POS, GX_DIRECT);
 	GX_SetVtxDesc(GX_VA_CLR0, GX_DIRECT);

	GX_LoadPosMtxImm(graph_modelview, GX_PNMTX0);
	GX_LoadNrmMtxImm(graph_modelview, GX_PNMTX0);

	GX_SetTevOp(GX_TEVSTAGE0, GX_PASSCLR);

	GX_Flush();
}

void DR_configure(u32 mode) {
	if (current_mode == mode)
		return;
	
	switch(mode) {
		case DRAW_CONF_TEXT:
			DR_configure_text();
			break;
		case DRAW_CONF_DRAW_TEXTURE:
			DR_configure_draw_with_texture();
			break;
		case DRAW_CONF_DRAW_NO_TEXTURE:
			DR_configure_draw_without_texture();
			break;
		default:
			return;
	}
	
	current_mode = mode;
}

static void va_DR_draw_text(f32 x, f32 y, f32 scale, GXColor c, u16 style, const char *s, va_list &ap) {
	char line[LARGEST_DRAWABLE_TEXT];
	wchar_t line_wchar[LARGEST_DRAWABLE_TEXT];

	vsnprintf(line, LARGEST_DRAWABLE_TEXT, s, ap);

	char *src = line;
	wchar_t *dst = line_wchar;
	while((*dst++ = *src++));

	Mtx text_mtx;
	guMtxScaleApply(text_modelview, text_mtx, scale / ((f32)FONT_SIZE), scale / ((f32)FONT_SIZE), 0.0);
	guMtxTransApply(text_modelview, text_mtx, x, y, 0.0);
	
	GX_LoadPosMtxImm(text_mtx, GX_PNMTX0);
	GX_LoadNrmMtxImm(text_mtx, GX_PNMTX0);

	freeTypeGX->drawText(0, 0, line_wchar, c, style);

	GX_LoadPosMtxImm(text_modelview, GX_PNMTX0);
	GX_LoadNrmMtxImm(text_modelview, GX_PNMTX0);
}

void DR_draw_text(f32 x, f32 y, f32 scale, const char *s, ...) {
	va_list ap;
	va_start(ap, s);
	va_DR_draw_text(x, y, scale, (GXColor){255, 255, 255, 255}, FTGX_NULL, s, ap);
	va_end(ap);
}

void DR_draw_text(f32 x, f32 y, f32 scale, GXColor c, const char *s, ...) {
	va_list ap;
	va_start(ap, s);
	va_DR_draw_text(x, y, scale, c, FTGX_NULL, s, ap);
	va_end(ap);
}

void DR_draw_text(f32 x, f32 y, f32 scale, GXColor c, u16 style, const char *s, ...) {
	va_list ap;
	va_start(ap, s);
	va_DR_draw_text(x, y, scale, c, style, s, ap);
	va_end(ap);
}

f32 DR_convert_cursor_x(f32 x) {
	return ((f32)MAP_SIZE) * 4.0 * x / (3.0 * screen_width) - ((f32)MAP_SIZE) / 2.0;
}

f32 DR_convert_cursor_y(f32 y) {
	return ((f32)MAP_SIZE) * ((screen_height - y - 1.0) / screen_height) - ((f32)MAP_SIZE) / 2.0;
}
